import numpy as np
from copy import deepcopy
from enum import Enum
import random
import math
class Direction:
    UP = 0
    DOWN = 1
    LEFT = 2
    RIGHT = 3

class Block:

    _value = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]



    def __init__(self, value):
        self._value = np.array(deepcopy(value));
        self.playerTurn = True

    def __str__(self):
        return str(self._value);


    def clone(self):
        b = Block(self._value);
        b.playerTurn = self.playerTurn
        return b;


    def rightOne(self, row):
        cur = 0;
        while cur < 4:
            # 为0直接不需要考虑
            if(self._value[row][cur] == 0):
                cur += 1;
                continue;
            for i in range(cur + 1, 4):
                if self._value[row][i] != 0 and self._value[row][i] != self._value[row][cur]:
                    break;
                # 找到撞击位置
                if self._value[row][i] == self._value[row][cur]:
                    self._value[row][cur] = 0;
                    self._value[row][i] += 1;
                    # 直接找下一个位置
                    cur = i;
                    break;
            cur += 1;
        cur = -1
        # 找第一个0
        for i in range(3, -1, -1):
            if self._value[row][i] == 0:
                cur = i;
                break;
        for i in range(cur, -1, -1):
            if(self._value[row][i] != 0):
                self._value[row][cur] = self._value[row][i];
                self._value[row][i] = 0;
                cur -= 1;




    def right(self):
        for i in range(4):
            self.rightOne(i);

    def up(self):
        self._value = np.rot90(self._value, k = 1, axes = (1, 0))
        self.right()
        self._value = np.rot90(self._value, k = 1, axes = (0, 1))

    def down(self):
        self._value = np.rot90(self._value, k = 1, axes = (0, 1))
        self.right()
        self._value = np.rot90(self._value, k = 1, axes = (1, 0))

    def left(self):
        self._value = np.rot90(self._value, k = 2, axes = (0, 1))
        self.right()
        self._value = np.rot90(self._value, k = 2, axes = (1, 0))

    def move(self, direction):
        if direction == Direction.UP:
            self.up()
        elif direction == Direction.DOWN:
            self.down()
        elif direction == Direction.LEFT:
            self.left()
        elif direction == Direction.RIGHT:
            self.right()

    def print(self):
        for v in self._value:
            print("%4s %4s %4s %4s" % tuple(map(lambda x:2**x if x > 0 else 0, v)))
        print()

    def randomFill(self):
        poss = self.availablePos()
        if(len(poss) == 1):
            pos = poss[0]
        else:
            pos = poss[random.randint(0, len(poss) - 1)];
        self._value[pos[0]][pos[1]] = random.randint(1, 2)

    def isDead(self):
        for i in range(4):
            for j in range(4):
                if self._value[i][j] == 0:
                    return False;
                if j > 0 and self._value[i][j - 1] == self._value[i][j]:
                    return False;
                if i > 0 and self._value[i - 1][j] == self._value[i][j]:
                    return False;
                if i < 3 and self._value[i + 1][j] == self._value[i][j]:
                    return False;
                if j < 3 and self._value[i][j + 1] == self._value[i][j]:
                    return False;
        return True;

    def isWin(self, num = 11):
        for i in range(4):
            for j in range(4):
                if self._value[i][j] >= num:
                    return True;
        return False;

    def maxValue(self):
        return np.max(self._value)

    def getDelta(self, dire):
        res = {Direction.UP:{'y' : -1, 'x' : 0}, Direction.DOWN : {'y' : 1, 'x' : 0}, Direction.LEFT : {'y' : 0, 'x' : -1}, Direction.RIGHT : {'y' : 0, 'x' : 1}}
        return res[dire]

    def inBounds(self, y, x):
        return y >= 0 and y <= 3 and x >= 0 and x <= 3;

    def findFarthestPosition(self, y, x, dire):
        
        delta = self.getDelta(dire)
        y += delta['y'];
        x += delta['x'];
        res = (y, x);
        while self.inBounds(y, x) and self._value[y][x] == 0:
            
            y += delta['y'];
            x += delta['x'];
            res = (y, x)
        return res

    def smoothness(self):
        smoothness = 0;
        for y in range(4):
            for x in range(4):
                if self._value[y][x] != 0:
                    # 当前值
                    value = self._value[y][x];
                    targetPos = self.findFarthestPosition(y, x, Direction.DOWN);
                    
                    if self.inBounds(targetPos[0], targetPos[1]) and self._value[targetPos[0]][targetPos[1]] != 0:
                        target = self._value[targetPos[0]][targetPos[1]];
                        smoothness -= abs(value - target);
                    targetPos = self.findFarthestPosition(y, x, Direction.RIGHT);
                    # print(targetPos)
                    # print(value)
                    if self.inBounds(targetPos[0], targetPos[1]) and self._value[targetPos[0]][targetPos[1]] != 0:
                        target = self._value[targetPos[0]][targetPos[1]];
                        smoothness -= abs(value - target);
        return smoothness;

    def setPos(self, y, x, value):
        self._value[y][x] = value;

    def emptyCount(self):
        res = 0
        for i in range(4):
            for j in range(4):
                res += 1 if self._value[i][j] == 0 else 0;
        return res;

    def monotonicity(self):
        totals = [0, 0, 0, 0]
        for y in range(4):
            current = 0
            nxt = current + 1;
            while nxt < 4:
                while nxt < 4 and self._value[y][nxt] == 0:
                    nxt += 1;
                if nxt >= 4:
                    nxt -= 1
                currentValue = self._value[y][current]
                nextValue = self._value[y][nxt];
                if currentValue > nextValue:
                    totals[0] += nextValue - currentValue;
                elif currentValue < nextValue:
                    totals[1] += currentValue - nextValue;
                current = nxt;
                nxt += 1;
        for x in range(4):
            current = 0
            nxt = current + 1;
            while nxt < 4:
                while nxt < 4 and self._value[nxt][x] == 0:
                    nxt += 1;
                if nxt >= 4:
                    nxt -= 1
                currentValue = self._value[current][x]
                nextValue = self._value[nxt][x];
                if currentValue > nextValue:
                    totals[2] += nextValue - currentValue;
                elif currentValue < nextValue:
                    totals[3] += currentValue - nextValue;
                current = nxt;
                nxt += 1;
        return max(totals[0], totals[1]) + max(totals[2], totals[3])

    def availablePos(self):
        res = []
        for y in range(4):
            for x in range(4):
                if(self._value[y][x] == 0):
                    res.append((y, x));
        return res;
    # 判断孤岛数量，孤岛为相同值的连续块
    def islands(self):
        visited = []
        res = 0
        def marks(y, x, value):
            if y <= 3 and y >= 0 and x <= 3 and x >= 0 and not visited[y][x] and self._value[y][x] == value:
                visited[y][x] = True

                for direction in [Direction.UP, Direction.DOWN, Direction. LEFT, Direction. RIGHT]:
                    delta = self.getDelta(direction);
                    marks(y + delta['y'], x + delta['x'], value);
        # 初始化访问矩阵
        for i in range(4):
            visited.append([False for i in range(4)])
        for y in range(4):
            for x in range(4):
                # 如果该位置有元素并且没有访问过
                if self._value[y][x] != 0 and not visited[y][x]:
                    res += 1;
                    marks(y, x, self._value[y][x])
        return res;
    def __eq__(x, y):
        # print(str(x._value))
        # print(str(y._value))
        return str(x._value) == str(y._value);

    def __hash__(self):
        return hash(str(self._value))

if __name__ == '__main__':
    # b = Block([
    #     [3,2,1,2],
    #     [4,1,2,1],
    #     [5,6,3,2],
    #     [3,1,2,1]])
    # print(b.isWin(4))
    # print(b.isDead())
    # # print(str(b))
    # b.print()
    # b.down()
    # res = b.randomFill()
    # for r in res:
    #     r.print()
    # print(b.isDead())


    b = Block([
        [1,0,0,0],
        [0,0,0,2],
        [0,0,0,2],
        [0,0,3,1]])
    # print(b.islands())
    # c = b.clone()
    # c.up()
    # b.print()
    # c.print()
    print(b.availablePos())
    print(b.smoothness())
    print(b.monotonicity())
    print(b.emptyCount())
    print(b.maxValue())
    print(b.smoothness() * 0.1 + b.monotonicity() + math.log(b.emptyCount()) * 2.7 + b.maxValue())
    print(b.islands())
    # print(b.monotonicity())
    # b.up();
    # b.randomFill()
    # b.print()
    # b.left()
    # b.randomFill()
    # b.print()
    # b.right()
    # b.randomFill()
    # b.print()
    # print(b.maxValue())
    # print(b.findFarthestPosition(3, 0, Direction.RIGHT))